﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YmtAuth.Common.Extend;
using YmtAuth.Domain.DomainEvent;
using YmtAuth.Domain.EventHistory;
using YmtAuth.Domain.Model.BlacklistUser;
using YmtAuth.Domain.Repository;
using YmtAuth.Domain.Shard;
using YmtSystem.CrossCutting;
using YmtSystem.Infrastructure.EventBusService;

namespace YmtAuth.Domain.Model.Safe
{
    /// <summary>
    /// 公共策略
    /// </summary>
    public class CommonStrategy
    {
        /// <summary>
        /// 部分用户启用安全认证流程
        /// </summary>
        public class SafePartialUserModeVerify : SafeVerifyStrategyBase
        {
            public override AuthSafeHandleCode Execute(UserLoginParameter par, UserLoginAuthRepository repo,
                AuthSafeConfiguration cfg)
            {
                var mode = cfg.SafeCfg.TryGet(SafeCfgKey.LoginSafe_Partial_Mode);
                if (mode.IsEmpty() || mode == "0")
                {
                    YmatouLoggingService.Debug("登录风控-灰度认证, 【LoginSafe_Partial_Mode】 is false; requestId: {0}", par.RequestId);
                    return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.ContinueNextVerify);
                }

                var val = par.UserId % mode.ConvertToInt32(1);
                if (val == 0)
                    return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.ContinueNextVerify);
                return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.AllowLogin);
            }
        }

        //登录密码历史检查
        public static AuthSafeHandleCode loginPasswordHistoryVerify(UserLoginParameter par, UserLoginAuthRepository repo,
            AuthSafeConfiguration cfg, int loginFailTimeMinutes, int loginFailCount)
        {
            AuthSafeHandleCode authSafeHandleCode = null;
            
            //获取登录历史密码错误统计
            var watch = Stopwatch.StartNew();
            bool needVerify = false;
            var _loginFailCount = 0L;

            //如果关闭请求计数器服务则从mongodb统计数据
            //if (ConfigurationManager.AppSettings["CounterService_R_LoginCount"].IsEmpty()
            //    || ConfigurationManager.AppSettings["CounterService_R_LoginCount"] == "0")
            //{
            //    _loginFailCount =
            //        repo.LoginHistoryRepo.Count(
            //            LoginEventSpecifications.MatchLoginPasswordFail(par.LoginId,
            //                loginFailTimeMinutes), LoginEventSpecifications.LoginLogDb,
            //            LoginEventSpecifications.LoginLogTb());
            //      needVerify   =_loginFailCount   >= loginFailCount;
            //}
            //else
            //{
            //    var count = repo.LoginHistoryRepo.GetUserLoginCount(LoginEventSpecifications.AUTHSERVICE_KEY,
            //        LoginEventSpecifications.GenerateLoginSpecKey(par.LoginId, AuthSafeHandleCode.LoginPasswordError)
            //        , LoginEventSpecifications.MatchLoginPasswordFailTime(loginFailTimeMinutes), DateTime.Now);
            //    if (count > -1)
            //    {
            //        _loginFailCount = count;
            //        needVerify = count >= loginFailCount;
            //    }
            //    else
            //    {
            //        //请求计数器服务失败，则重新从mongodb统计数据
            //        _loginFailCount =
            //            repo.LoginHistoryRepo.Count(
            //                LoginEventSpecifications.MatchLoginPasswordFail(par.LoginId,
            //                    loginFailTimeMinutes), LoginEventSpecifications.LoginLogDb,
            //                LoginEventSpecifications.LoginLogTb());
            //       needVerify =_loginFailCount >= loginFailCount;
            //    }
            //}

            //WESLEY: 20161025

            var count = repo.LoginHistoryRepo.GetUserLoginCount(LoginEventSpecifications.AUTHSERVICE_KEY,
                    LoginEventSpecifications.GenerateLoginSpecKey(par.LoginId, AuthSafeHandleCode.LoginPasswordError)
                    , LoginEventSpecifications.MatchLoginPasswordFailTime(loginFailTimeMinutes), DateTime.Now);
            if (count > -1)
            {
                _loginFailCount = count;
                needVerify = count >= loginFailCount;
            }

            watch.Stop();
            //创建响应代码及原因说明
            var code = needVerify ? AuthSafeHandleCode.NeedIdentityVerify : AuthSafeHandleCode.ContinueNextVerify;
            var desc = needVerify
                ? string.Format("登录历史密码错误{0}次,{1}分钟内大于等于{2},需要验证身份", _loginFailCount,loginFailTimeMinutes, loginFailCount)
                : string.Format("登录历史密码错误{0}次,{1}分钟内小于{2},执行下个安全验证", _loginFailCount, loginFailTimeMinutes, loginFailCount);
            
            authSafeHandleCode = AuthSafeHandleCode.CreateCode(code, desc);

            if (AuthSafeHandleCode.NeedIdentityVerify == code)
            {
                authSafeHandleCode = AuthSafeHandleCode.CreateCode(code
                    , AuthSafeHandleSubCode.NeedIdentityVerify_LoginSafe_LoginPasswordHistoryVerify
                    , desc
                    , YmatouBlacklistUserSpecifications.GetLogin_NeedIdentityVerifyMessage(par.Source));
            }

            //TODO 发布登录密码错误次数事件
            EventBus.Instance.Publish(new LoginSafeVerifyEvent
            {
                UserId = par.UserId,
                AppSource = par.Source,
                AuthType = "LoginPasswordHistoryVerify",
                ResponseCode = authSafeHandleCode.SubCode,
                LoginId = par.LoginId,
                LoginIp = par.Ip,
                Desc = desc,
                DeviceId = par.DeviceId,
                ClientId = par.AppClientId,
                RunTime = watch.Elapsed.TotalMilliseconds
            });            
            return authSafeHandleCode;
        }

        //用户最后一次IP区域认证检查
        public static AuthSafeHandleCode CheckUserIpAreaLastAuthTime(UserLoginParameter par
            , UserLoginAuthRepository repo, AuthSafeConfiguration cfg, int lastDay)
        {
            var watch = Stopwatch.StartNew();
            var rawIpRearInfo = repo.IpAreaRepo.FindIpAreaInfo(IpAreaInfoAuthEntity.IpConvertToLong(par.Ip));
            watch.Stop();
            if (rawIpRearInfo == null)
            {
                EventBus.Instance.Publish(new LoginSafeVerifyEvent
                {
                    UserId = par.UserId,
                    AppSource = par.Source,
                    AuthType = "VerifyIpAreaLastAuthTime",
                    ResponseCode = AuthSafeHandleCode.AllowLogin,
                    LoginId = par.LoginId,
                    LoginIp = par.Ip,
                    Desc = string.Format("{0}端Ip区域token最后认证时间检查，原始库不存在IP区域，允许登录", par.Source),
                    Summary = "-400",
                    RunTime = watch.Elapsed.TotalMilliseconds
                });

                YmatouLoggingService.Debug("登录风控-APP用户最后一次IP区域认证, 原始库不存在IP区域, requestId: {0}, loginid: {1}, source: {2}, ip: {3}", par.RequestId, par.LoginId, par.Source, par.Ip);
                return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.AllowLogin);
            }
            watch.Restart();
            var lastAuthIpAreaInfo = repo.IpAreaRepo.FindUserLastTokenAuthIpAreaInfo(par.UserId, rawIpRearInfo.Country,
                rawIpRearInfo.Province, rawIpRearInfo.City);
            watch.Stop();
            if (lastAuthIpAreaInfo == null)
            {
                EventBus.Instance.Publish(new LoginSafeVerifyEvent
                {
                    UserId = par.UserId,
                    AppSource = par.Source,
                    AuthType = "VerifyIpAreaLastAuthTime",
                    ResponseCode = AuthSafeHandleCode.AllowLogin,
                    LoginId = par.LoginId,
                    LoginIp = par.Ip,
                    Desc = string.Format("{0}_最后一次IP区域认证数据不存在,允许登录", par.Source),
                    Summary = "-400",
                    RunTime = watch.Elapsed.TotalMilliseconds
                });

                YmatouLoggingService.Debug("登录风控-APP端用户最后一次IP区域认证, IP区域认证数据不存在, requestId: {0}, loginid: {1}, source: {2}, ip: {3}", par.RequestId, par.LoginId, par.Source, par.Ip);
                return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.AllowLogin);
            }
            if (lastAuthIpAreaInfo.AuthTime == null)
            {
                string msg = string.Format("用户 {0} 最后一次Ip  {1} 区域认证,认证时间为空,执行身份认证", par.LoginId, par.Ip);
                YmatouLoggingService.Debug(msg);
                return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.NeedIdentityVerify, msg);
            }
            var subDay = DateTime.Now.Subtract(lastAuthIpAreaInfo.AuthTime.Value).TotalDays;

            var code = subDay >= lastDay
                ? AuthSafeHandleCode.NeedIdentityVerify
                : AuthSafeHandleCode.AllowLogin;
            var desc = subDay >= lastDay
                ? string.Format("用户{0}最后一次Ip区域认证时间{1}大于等于{2}天，执行身份验证", par.LoginId, lastAuthIpAreaInfo.AuthTime.Value, lastDay)
                : string.Format("用户{0}最后一次Ip区域认证时间{1}小于{2}天，允许登录", par.LoginId, lastAuthIpAreaInfo.AuthTime.Value, lastDay);

            AuthSafeHandleCode res = null;
            if (AuthSafeHandleCode.NeedIdentityVerify == code)
            {
                res = AuthSafeHandleCode.CreateCode(code
                    , AuthSafeHandleSubCode.NeedIdentityVerify_LoginSafe_VerifyIpAreaLastAuthTime
                    , desc
                    , YmatouBlacklistUserSpecifications.GetLogin_NeedIdentityVerifyMessage(par.Source));
            }
            else
            {
                res = AuthSafeHandleCode.CreateCode(code, desc);
            }

            //TODO 发布最后一次token认证时间事件
            EventBus.Instance.Publish(new LoginSafeVerifyEvent
            {
                UserId = par.UserId,
                AppSource = par.Source,
                AuthType = "VerifyAuthTokenLastTime",
                ResponseCode = res.SubCode,
                LastAuthTime = lastAuthIpAreaInfo.AuthTime.Value,
                LoginId = par.LoginId,
                LoginIp = par.Ip,
                Desc = desc,
                RunTime = watch.Elapsed.TotalMilliseconds
            });

            YmatouLoggingService.Debug("登录风控-用户最后一次IP区域认证, {0}; code: {1}, requestId: {2}", desc, code, par.RequestId);

            return res;
        }

        //IP区域检查
        public static AuthSafeHandleCode CheckUserIpArea(UserLoginParameter par
            , UserLoginAuthRepository repo, AuthSafeConfiguration cfg)
        {
            //获取IP区域原始数据,不存在则不需要身份验证
            var watch = Stopwatch.StartNew();
            var ipAreaRawInfo = repo.IpAreaRepo.FindIpAreaInfo(IpAreaInfoAuthEntity.IpConvertToLong(par.Ip));
            watch.Stop();
            if (ipAreaRawInfo == null)
            {
                //TODO 发布原始IP区域数据不存在事件
                EventBus.Instance.Publish(new LoginSafeVerifyEvent
                {
                    UserId = par.UserId,
                    AppSource = par.Source,
                    AuthType = "RawIpNotFind",
                    ResponseCode = AuthSafeHandleCode.RawIpNotFind,
                    LoginId = par.LoginId,
                    LoginIp = par.Ip,
                    Desc = "IP库原始数据不存在,执行下个安全认证",
                    RunTime = watch.Elapsed.TotalMilliseconds
                });
                YmatouLoggingService.Debug("user {0},ip {1} raw ip not find ,login source {2}", par.LoginId, par.Ip,
                    par.Source);
                return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.RawIpNotFind, "IP库原始数据不存在");
            }
            //开启用户第一次登录，自动初始化该IP为常用登录Ip
            var autoAuth = cfg.SafeCfg.TryGet(SafeCfgKey.LoginSafe_AppUserFirstLogin_AutoAuthSuccess, "0");
            if (autoAuth == "1" && UserIpAreaFirstAutoAuth(par, ipAreaRawInfo, repo.IpAreaRepo))
            {
                return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.UserFirstLogin, "用户第一次登录");
            }
            //获取IP区域认证数据,不存在则需要身份验证 
            watch.Restart();
            var ipAreaInfo = repo.IpAreaRepo.FindAreaInfoAuthEntity(par.UserId, ipAreaRawInfo.Country,
                ipAreaRawInfo.Province, ipAreaRawInfo.City);
            watch.Stop();
            var code = AuthSafeHandleCode.ContinueNextVerify;
            var desc = "";
            if (ipAreaInfo == null)
            {
                code = cfg.SafeCfg.TryGet(SafeCfgKey.LoginSafe_PcIp_NotExistsIdentityVerify, "100").ConvertToInt32(100);
                desc = string.Format("该Ip区域数据不存在，执行Code_{0}", code);
                EventBus.Instance.Publish(new LoginSafeVerifyEvent
                {
                    UserId = par.UserId,
                    AppSource = par.Source,
                    AuthType = "IpAreaNotExists",
                    ResponseCode = AuthSafeHandleSubCode.NeedIdentityVerify_LoginSafe_CannotFindIpArea,
                    IpArea = ipAreaRawInfo.GetArea(),
                    LoginId = par.LoginId,
                    LoginIp = par.Ip,
                    Desc = string.Format("{0}", desc),
                    RunTime = watch.Elapsed.TotalMilliseconds
                });

                YmatouLoggingService.Debug("登录风控-IP区域检查, IP区域不存在; requestId: {0}", par.RequestId);

                return AuthSafeHandleCode.CreateCode(code, AuthSafeHandleSubCode.NeedIdentityVerify_LoginSafe_CannotFindIpArea, desc);
            }

            return AuthSafeHandleCode.CreateCode(AuthSafeHandleCode.ContinueNextVerify);
        }

        //IP区域状态验证
        public static AuthSafeHandleCode VerifyIPAreaStatus(UserLoginParameter par, AuthSafeConfiguration cfg
            , IpAreaInfoAuthEntity ipAreaInfo, int statusSuccessCode, int statusFailCode)
        {

            //IP 区域未认证，认证失败需要用户身份验证
            var watch = Stopwatch.StartNew();
            var code = 0;
            var desc = "";
            if (!ipAreaInfo.AuthIsSuccess())
            {
                code = statusFailCode;
                desc = string.Format("IP区域认证未认证或认证失败,执行Code_{0}", code);
            }
            else
            {
                code = statusSuccessCode;
                desc = string.Format("Ip区域认证成功，执行Code_{0}", code);
            }
            watch.Stop();

            AuthSafeHandleCode res = null;

            if (AuthSafeHandleCode.NeedIdentityVerify == code)
            {
                res = AuthSafeHandleCode.CreateCode(code
                    , AuthSafeHandleSubCode.NeedIdentityVerify_LoginSafe_VerifyIPAreaStatus
                    , desc
                    , YmatouBlacklistUserSpecifications.GetLogin_NeedIdentityVerifyMessage(par.Source));
            }
            else
            {
                res = AuthSafeHandleCode.CreateCode(code, desc);
            }

            //TODO 发布IP区域认证未认证或不通过事件
            EventBus.Instance.Publish(new LoginSafeVerifyEvent
            {
                UserId = par.UserId,
                AppSource = par.Source,
                AuthType = "VerifyIPAreaStatus",
                ResponseCode = res.SubCode,
                IpArea = ipAreaInfo.GetArea(),
                LoginId = par.LoginId,
                LoginIp = par.Ip,
                Desc = string.Format("{0}", desc),
                RunTime = watch.Elapsed.TotalMilliseconds
            });

            YmatouLoggingService.Debug("登录风控-IP区域状态验证, {0}; LoginId: {1}, Ip: {2}, AuthStatus: {3}, SubCode: {4}, requestId: {5}", desc, par.LoginId, par.Ip, ipAreaInfo.AuthStatus, res.SubCode, par.RequestId);

            return res;
        }

        //IP黑名单验证
        public static AuthSafeHandleCode IpBlackListVerify(UserLoginParameter par, UserLoginAuthRepository repo
            , int blackIpAction, string blackIpActionDesc)
        {
            var watch = Stopwatch.StartNew();
            var _ip = repo.IpBlackListRepo.FindOne(YmatouBlacklistUserSpecifications.MathRequestIP(par.Ip)
                , YmatouBlacklistUserSpecifications.GetBlacklistDbName()
                , YmatouBlacklistUserSpecifications.GetBlacklistTableName(par.Source));
            watch.Stop();
            var code = AuthSafeHandleCode.ContinueNextVerify;
            var desc = "此Ip不在黑名单,执行下个安全认证";
            //如果是黑名单且禁止登录时间未到期,则（根据配置）执行身份验证或者拒绝登录
            DateTime? expiredTime = null;
            if (_ip != null)
            {
                if (_ip.IsDisableRequest())
                {
                    code = blackIpAction;
                    desc = blackIpActionDesc;
                }
                else
                {
                    code = AuthSafeHandleCode.ContinueNextVerify;
                    desc = "此Ip在黑名单,锁定时间超时,执行下个安全认证";
                }

                expiredTime = _ip.ExpiredTime.ToLocalTime();
            }

            AuthSafeHandleCode res = null;
            if (AuthSafeHandleCode.NeedIdentityVerify == code)
            {
                res = AuthSafeHandleCode.CreateCode(code
                    , AuthSafeHandleSubCode.NeedIdentityVerify_LoginSafe_BlackIPVerify
                    , desc
                    , YmatouBlacklistUserSpecifications.GetLogin_NeedIdentityVerifyMessage(par.Source));
            }
            else
            {
                res = AuthSafeHandleCode.CreateCode(code, desc);
            }

            //TODO 发布用户IP黑名单事件
            EventBus.Instance.Publish(new LoginSafeVerifyEvent
            {
                UserId = par.UserId,
                AppSource = par.Source,
                AuthType = "IpBlackListVerify",
                ResponseCode = res.SubCode,
                ExpiredTime = expiredTime,
                LoginId = par.LoginId,
                LoginIp = par.Ip,
                Desc = string.Format("{0}", desc),
                RunTime = watch.Elapsed.TotalMilliseconds
            });

            return res;
        }

        //检查用户是否第一次登录
        private static bool UserIpAreaFirstAutoAuth(UserLoginParameter par, IpAreaInfoAuthEntity.IpAreaInfo rawAreaInfo,
            IIpAreaInfoRepository repo)
        {
            var watch = Stopwatch.StartNew();
            var firstAuthInfo = repo.FindUserFirstAuthAreaInfo(par.UserId, 200).FirstOrDefault();
            watch.Stop();
            //如果用户IP区域大于2条数据且存在非当前登录区域认证记录，则表示不是第一次登录
            if (firstAuthInfo != null)
            {
                if (firstAuthInfo.UserId == par.UserId && firstAuthInfo.Country == rawAreaInfo.Country &&
                    firstAuthInfo.Province == rawAreaInfo.Province && firstAuthInfo.City == rawAreaInfo.City
                    && firstAuthInfo.AuthStatus == -200)
                {
                    YmatouLoggingService.Debug("用户第一次登录IP区域已认证失败 {0},{1},{2}", par.UserId, par.Ip, par.Source);
                    return false;
                }
                if ((firstAuthInfo.Country != rawAreaInfo.Country ||
                     firstAuthInfo.Province != rawAreaInfo.Province
                     || firstAuthInfo.City != rawAreaInfo.City)
                    && firstAuthInfo.AuthStatus == 200)
                {
                    YmatouLoggingService.Debug("用户非第一次登录IP区域已认证,{0},{1},{2}", par.Source, par.UserId, par.Ip);
                    return false;
                }
            }
            else
                firstAuthInfo = new IpAreaInfoAuthEntity(par.UserId, rawAreaInfo.Country, rawAreaInfo.Province,
                    rawAreaInfo.City, par.Ip, par.Source);
            firstAuthInfo.SetAuthStatus(true, IPAreaSourceType.Login);
            repo.TrySaveChange(firstAuthInfo,
                ex =>
                    YmatouLoggingService.Error(
                        "TrySaveChange firstIPAreaAuthInfo userID:{0},Country:{1},Province:{2},City:{3},ex:{4}",
                        par.UserId,
                        rawAreaInfo.Country, rawAreaInfo.Province, rawAreaInfo.City, ex.ToString()));
            var desc = "用户IP区域第一次认证，自动认证成功";
            EventBus.Instance.Publish(new LoginSafeVerifyEvent
            {
                UserId = par.UserId,
                AppSource = par.Source,
                AuthType = "VerifyLoginIpArea",
                ResponseCode = AuthSafeHandleCode.UserIpAreaFirstAutoAuth,
                LoginId = par.LoginId,
                LoginIp = par.Ip,
                Desc = string.Format("{0},source,{1}", desc, par.Source),
                RunTime = watch.Elapsed.TotalMilliseconds
            });
            YmatouLoggingService.Debug("source {0},loginid {1},{2}", par.Source, par.LoginId, desc);
            return true;
        }

        //验证策略辅助

        public class VerifyStrategyHelp
        {
            public static string GetStrategyKey(string source, byte userType)
            {
                if (source.IsEmpty()) return "pc";
                if (source.ToLower() == "app") return "app";
                return "pc";
            }

            public static string App = "app";
            public static string pc = "pc";
            public static string BuyerPc = "buyerpc";
            public static string BuyerApp = "buyerapp";
        }
    }
}
